package pbbltobpel;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import pbbltobpel.parser.PddlPlan;
import pbbltobpel.parser.PlanStep;

import com.predic8.schema.Attribute;
import com.predic8.schema.ComplexType;
import com.predic8.schema.Schema;
import com.predic8.schema.Sequence;
import com.predic8.schema.SimpleType;
import com.predic8.schema.restriction.BaseRestriction;
import com.predic8.schema.restriction.facet.EnumerationFacet;
import com.predic8.wsdl.Binding;
import com.predic8.wsdl.BindingOperation;
import com.predic8.wsdl.Definitions;
import com.predic8.wsdl.Message;
import com.predic8.wsdl.Operation;
import com.predic8.wsdl.Part;
import com.predic8.wsdl.PortType;
import com.predic8.wsdl.Service;
import com.predic8.wsdl.Types;

public class WSDLGenerator {
	
	public static Element createSimpleElement(Document doc,  SimpleType st)
	{
        Element elem = doc.createElement("simpleType");
		
        if (st.getName() != null && !st.getName().isEmpty())
        	elem.setAttribute("name", st.getName());
        
        BaseRestriction res = st.getRestriction();
        Element resElem = doc.createElement("restriction");
        if (res.getBase() != null)
        {
			String type = "";
			if (!res.getBase().getPrefix().isEmpty())
				type = res.getBase().getPrefix()+":";
			else if (res.getBase().getLocalPart().equals("int"))
				type = "xsd:";
			else
				type = "tns:";

			type += res.getBase().getLocalPart();

        	resElem.setAttribute("base", type);

        }
        
        for (EnumerationFacet e : res.getEnumerationFacets())
        {
        	Element enumElem = doc.createElement("enumeration");
        	
        	enumElem.setAttribute("value", e.getValue());
        	
        	resElem.appendChild(enumElem);
        }
        
        elem.appendChild(resElem);
        
		return elem;
	}

	public static Element createComplexElement(Document doc, ComplexType ct)
	{
        Element elem = doc.createElement("complexType");

        if (ct.getName() != null && !ct.getName().isEmpty())
        	elem.setAttribute("name", ct.getName());
        
        Sequence sequence = ct.getSequence();
        Element sequenceElem = doc.createElement("sequence");
        if (sequence == null) // ignore for now
        	return null;
        for (com.predic8.schema.Element e : sequence.getElements())
        {
        	Element elem1 = doc.createElement("element");
        	if (!e.getName().isEmpty())
        		elem1.setAttribute("name", e.getName());

        	if (!e.getMaxOccurs().isEmpty())
        		elem1.setAttribute("maxOccurs", e.getMaxOccurs());
        		
			if (e.getType() == null)
			{				
				if (e.getEmbeddedType() instanceof ComplexType) {
					Element complexElement = createComplexElement(doc, (ComplexType)e.getEmbeddedType());
					elem1.appendChild(complexElement);
				}
				else if (e.getEmbeddedType() instanceof SimpleType) {
					Element simpleElement = createSimpleElement(doc, (SimpleType)e.getEmbeddedType());
					elem1.appendChild(simpleElement);
				}
			}
			else
			{
				String type = "";
				if (!e.getType().getPrefix().isEmpty())
					type = e.getType().getPrefix()+":";
				else if (e.getType().getLocalPart().equals("int"))
					type = "xsd:";
				else
					type = "tns:";

				type += e.getType().getLocalPart();

				elem1.setAttribute("type", type);
				
			}
			sequenceElem.appendChild(elem1);
        }
        
        elem.appendChild(sequenceElem);
        
        for (Attribute attr : ct.getAttributes())
        {
        	Element attrElem = doc.createElement("attribute");
        	if (!attr.getName().isEmpty())
        		attrElem.setAttribute("name", attr.getName());
        	if (attr.getType() != null)
        	{
				String type = "";
				if (!attr.getType().getPrefix().isEmpty())
					type = attr.getType().getPrefix()+":";
				else if (attr.getType().getLocalPart().equals("int"))
					type = "xsd:";
				else
					type = "tns:";
				
				type += attr.getType().getLocalPart();

				attrElem.setAttribute("type", type);

        	}
        	if (!attr.getUse().isEmpty())
        		attrElem.setAttribute("use", attr.getUse());
        	
        	elem.appendChild(attrElem);
        }
        
        return elem;
	}
	
	public static Document getWSDLFile(PddlPlan plan, ParameterTypeMapper mapper) throws ParserConfigurationException
	{
		DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
        Document doc = docBuilder.newDocument();
        
        String name = plan.getFileName();
        Element root = doc.createElement("definitions");
        root.setAttribute("xmlns:tns", "http://" + name);
        root.setAttribute("name", name);
        root.setAttribute("targetNamespace", "http://" + name);
        root.setAttribute("xmlns", "http://schemas.xmlsoap.org/wsdl/");
        root.setAttribute("xmlns:plnk", "http://docs.oasis-open.org/wsbpel/2.0/plnktype");
        root.setAttribute("xmlns:soap", "http://schemas.xmlsoap.org/wsdl/soap/");
        root.setAttribute("xmlns:vprop", "http://docs.oasis-open.org/wsbpel/2.0/varprop");
        root.setAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
        doc.appendChild(root);
        
       
              
        HashSet<String> set = new HashSet<String>();
        int index = 0;
        
        Hashtable<String, Element> typesElems = new Hashtable<String, Element>();
        
        for(String key : plan.getWsdls().keySet())
        {
        	Definitions def = plan.getWsdls().get(key);
        	String namespace = def.getTargetNamespace();
        	String nskey = "wsdl" + index++;
        	
        	if(!set.contains(namespace))
        	{
        		set.add(namespace);
        		root.setAttribute("xmlns:" + nskey, namespace);
        	}
        	
			for (Types t : def.getAllTypes())
			{
				for (Schema s : t.getAllSchemas())
				{
					for (com.predic8.schema.Element e : s.getElements())
					{
						if (typesElems.containsKey(e.getName()))
							continue;
						
						if (e.getEmbeddedType() instanceof ComplexType) {
							Element complexElement = createComplexElement(doc, (ComplexType)e.getEmbeddedType());
							if (complexElement != null) 
							{
								if (complexElement.getAttribute("name").isEmpty())
									complexElement.setAttribute("name", e.getName());
								typesElems.put(e.getName(), complexElement);
							}
						}
						else if (e.getEmbeddedType() instanceof SimpleType) {
							Element simpleElement = createSimpleElement(doc, (SimpleType)e.getEmbeddedType());
							if (simpleElement != null) 
							{
								if (simpleElement.getAttribute("name").isEmpty())
									simpleElement.setAttribute("name", e.getName());
								typesElems.put(e.getName(), simpleElement);
							}
						}
					}
					
					for (SimpleType st : s.getSimpleTypes())
					{
						if (typesElems.containsKey(st.getName()))
							continue;

						typesElems.put(st.getName(), createSimpleElement(doc, st));
					}

					for (ComplexType ct : s.getComplexTypes())
					{
						if (typesElems.containsKey(ct.getName()))
							continue;

						Element complexElement = createComplexElement(doc, ct);
						if (complexElement != null)
							typesElems.put(ct.getName(), complexElement);
					}
				}
			}
        	
        	for(Service s : def.getServices())
        	{
        		Element pt = doc.createElement("plnk:partnerLinkType");
            	pt.setAttribute("name", s.getName() + "LinkType");
            	root.appendChild(pt);
            	
            	Element role = doc.createElement("plnk:role");
            	role.setAttribute("name", s.getName() + "Role");
            	role.setAttribute("portType", nskey + ":" + s.getPorts().get(0).getBinding().getPortType().getName());
            	pt.appendChild(role);
        	}
        }
        
        for(String key : plan.getWsdls().keySet())
        {
        	Definitions def = plan.getWsdls().get(key);
        	String namespace = def.getTargetNamespace();
       		Element imp = doc.createElement("import");
               imp.setAttribute("namespace", namespace);

            imp.setAttribute("location", "file:///"+ key);
                
            root.appendChild(imp);
         }

        Element types = doc.createElement("types");
        root.appendChild(types);
        
        Element schema = doc.createElement("schema");
        schema.setAttribute("xmlns", "http://www.w3.org/2001/XMLSchema");
        schema.setAttribute("attributeFormDefault", "unqualified");
//        schema.setAttribute("elementFormDefault", "qualified");
        schema.setAttribute("targetNamespace", "http://" + name);
        types.appendChild(schema);
        
        for (Element typeElem : typesElems.values())
        	schema.appendChild(typeElem);
        
        //Messages
        Element m2 = doc.createElement("message");
        m2.setAttribute("name", name + "RequestMessage");
        root.appendChild(m2);
        
        Set<String> addedMessages = new HashSet<String>();
		for(PlanStep step : plan.getSteps())
		{
			List<String> params = step.getParams();
			for (String param : params)
			{
				// input
				if (param.contains("Ontology1.owl#"))
				{
					String partName = param.substring(1 + param.indexOf("#"));
					if (addedMessages.contains(partName))
						continue;
								
			        Element part = doc.createElement("part");
			        part.setAttribute("type", mapper.getParamType(partName));
			        part.setAttribute("name", partName);
			        m2.appendChild(part);
			
			        addedMessages.add(partName);
				}
			}
		}	

        Element m1 = doc.createElement("message");
        m1.setAttribute("name", name + "ResponseMessage");
        root.appendChild(m1);
        

		PlanStep lastStep = plan.getSteps().get(plan.getSteps().size() - 1);
		String lastService = lastStep.getName().substring(1 + lastStep.getName().lastIndexOf("/"),
				lastStep.getName().lastIndexOf("."));

        for(String key : plan.getWsdls().keySet())
        {
        	Definitions def = plan.getWsdls().get(key);
        	if (!def.getName().equals(lastService))
        		continue;
        	
        	String outMessage = "";
			for(Binding b : def.getBindings())
			{
				if (b.getOperations().isEmpty())
					continue;
				BindingOperation o = b.getOperations().get(0);

				outMessage = o.getOutput().getName();
			}

			Message message = null;
			for(PortType p : def.getPortTypes())
			{
				for (Operation o : p.getOperations())
				{
					if (!o.getOutput().getName().equals(outMessage))
						continue;

					message = o.getOutput().getMessage();
				}
			}
			for (Part part : message.getParts())
			{
		        Element partElement = doc.createElement("part");
				if (part.getType() != null)
				{
					String type = "";
					if (!part.getType().getPrefix().isEmpty())
						type = part.getType().getPrefix()+":";
					type += part.getType().getLocalPart();

					partElement.setAttribute("type", type);
				} else if (part.getElement() != null && !part.getElement().isEmpty())
				{
					partElement.setAttribute("type", part.getElement());
				}
				
		        partElement.setAttribute("name", part.getName());
		        m1.appendChild(partElement);	
			}
        }
			               
        //Port types
        
        Element portType = doc.createElement("portType");
        portType.setAttribute("name", name);
        root.appendChild(portType);
        
        Element operation = doc.createElement("operation");
        operation.setAttribute("name", "process");
        portType.appendChild(operation);
        
        Element input = doc.createElement("input");
        input.setAttribute("message", "tns:" + name + "RequestMessage");
        operation.appendChild(input);
        
        Element output = doc.createElement("output");
        output.setAttribute("message", "tns:" + name + "ResponseMessage");
        operation.appendChild(output);
        
        //PartnerLink
        
        Element partnerLink = doc.createElement("plnk:partnerLinkType");
        partnerLink.setAttribute("name", name);
        root.appendChild(partnerLink);
        
        Element role = doc.createElement("plnk:role");
        role.setAttribute("name", name + "Provider");
        role.setAttribute("portType", "tns:" + name);
        partnerLink.appendChild(role);
        
        //Binding
        Element binding = doc.createElement("binding");
        binding.setAttribute("name", name + "Binding");
        binding.setAttribute("type", "tns:" + name);
        root.appendChild(binding);
        
        Element sbinding = doc.createElement("soap:binding");
        sbinding.setAttribute("style", "document");
        sbinding.setAttribute("transport", "http://schemas.xmlsoap.org/soap/http");
        binding.appendChild(sbinding);
        
        Element operation1 = doc.createElement("operation");
        operation1.setAttribute("name", "process");
        binding.appendChild(operation1);

        
        Element binput = doc.createElement("input");
        operation1.appendChild(binput);
        
        Element ib = doc.createElement("soap:body");
        ib.setAttribute("use", "literal");
        binput.appendChild(ib);
        
        Element boutput = doc.createElement("output");
        operation1.appendChild(boutput);
        
        Element ob = doc.createElement("soap:body");
        ob.setAttribute("use", "literal");
        boutput.appendChild(ob);
        
        //Service
        Element service = doc.createElement("service");
        service.setAttribute("name", name + "Service");
        root.appendChild(service);
        
        Element port = doc.createElement("port");
        port.setAttribute("binding", "tns:" + name + "Binding");
        port.setAttribute("name", name + "Port");
        service.appendChild(port);
        
        Element saddress = doc.createElement("soap:address");
        saddress.setAttribute("location", "http://localhost:8080/ode/processes/" + name);
        port.appendChild(saddress);
        
        
        return doc;
	}
	
	
}
